Skip to content

ci: add zizmor for GitHub Actions security analysis#2648

Merged
Kludex merged 2 commits into
mainfrom
ci/add-zizmor
May 20, 2026
Merged

ci: add zizmor for GitHub Actions security analysis#2648
Kludex merged 2 commits into
mainfrom
ci/add-zizmor

Conversation

@Kludex
Copy link
Copy Markdown
Member

@Kludex Kludex commented May 20, 2026

Summary

  • Add a zizmor workflow that audits all GitHub Actions workflows on push to main and on PRs, mirroring the setup in uvicorn.
  • Address every finding zizmor reports against the existing workflows so the new job is green from the start:
    • Add persist-credentials: false to all actions/checkout invocations (artipacked).
    • Add top-level permissions: contents: read to publish-pypi.yml (excessive-permissions).
    • Move github.event.release.* and steps.*.outputs.result template expansions in comment-on-release.yml into env: vars consumed via process.env (template-injection).
  • The one remaining warning (secrets-outside-env on the Claude Code action) is suppressed inline because the proper fix is to put ANTHROPIC_API_KEY behind a dedicated GitHub environment, which is a repo-settings change rather than a code change. Happy to follow up if a maintainer creates the environment.

Ran uvx zizmor .github/workflows/ locally; reports No findings to report. Good job! after these changes.

Test plan

  • CI passes, including the new GitHub Actions Security Analysis job.
  • On the next release, comment-on-release.yml still comments on the right PRs (the script logic is unchanged, just reads values from process.env).

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.

Run zizmor on every push to main and on PRs. Address all findings:

- Add persist-credentials: false to all actions/checkout invocations
- Add top-level permissions: contents: read to publish-pypi.yml
- Convert github.event.release.* template expansions in
  comment-on-release.yml to env-var indirection in github-script to
  avoid code injection
- Suppress the secrets-outside-env warning on the Claude Code action
  with an inline zizmor: ignore; moving to a dedicated GitHub
  environment is a separate infrastructure change
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@Kludex Kludex requested a review from maxisbey May 20, 2026 07:47
uses: anthropics/claude-code-action@2f8ba26a219c06cfb0f468eef8d97055fa814f97 # v1.0.53
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # zizmor: ignore[secrets-outside-env]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the point is to create an environment and add the ANTHROPIC_API_KEY in there?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - that's the proper fix, but it needs a repo-settings change (create a GitHub environment with ANTHROPIC_API_KEY) which is out of scope for this code-only PR. Suppressed inline for now; happy to follow up once the environment exists.

Comment thread .github/workflows/publish-pypi.yml
The newer zizmor (1.25.x, shipped by zizmor-action v0.5.6) adds a
cache-poisoning audit that flags astral-sh/setup-uv with
enable-cache: true in a release: published-triggered job. Disable the
cache for the release-build job - release builds are infrequent and a
cold cache is fine - so the new security workflow lands clean.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — the earlier cache-poisoning finding has been addressed (enable-cache: false + zizmor-action v0.5.6); the one remaining inline comment is a cosmetic nit about a no-op suppression.

Extended reasoning...

Overview

This PR adds a zizmor security-analysis workflow (.github/workflows/zizmor.yml, pinned to zizmorcore/zizmor-action@v0.5.6 with permissions: {} at top level and only security-events: write on the job) and hardens the existing workflows so the new check is green: persist-credentials: false on every actions/checkout, top-level permissions: contents: read on publish-pypi.yml, enable-cache: false on the release build, and the comment-on-release.yml template-injection fix that moves github.event.release.* / steps.*.outputs.result expansions into env: vars consumed via process.env (with JSON.parse for the structured outputs).

Security risks

All changes are net hardening: tighter token scopes, no persisted git credentials, no cache reuse in the publish path, and JS string interpolation replaced by env-var consumption. The JSON.parse(process.env.PREVIOUS_TAG_JSON) / JSON.parse(process.env.PR_NUMBERS_JSON) rewrites are correct because actions/github-script JSON-serializes its return value into outputs.result, so the parsed values match what the inline-template form previously evaluated to. No new permissions are granted anywhere.

Level of scrutiny

CI/CD config touching the PyPI publish pipeline normally warrants care, but every edit here is restrictive rather than permissive, so the blast radius of a mistake is a broken (not over-privileged) workflow. The zizmor.yml workflow itself is a near-verbatim copy of upstream uvicorn's reference setup.

Other factors

My earlier inline comment about the high-severity cache-poisoning finding in publish-pypi.yml has been addressed in the latest commit (enable-cache: false, action bumped to v0.5.6). The remaining bug-hunter finding is a cosmetic nit — the # zizmor: ignore[secrets-outside-env] annotation in claude.yml is a no-op under the default regular persona — which doesn't affect correctness or security and can be cleaned up in a follow-up. Maintainers (Kludex, maxisbey) have been actively reviewing and steering the PR.

uses: anthropics/claude-code-action@2f8ba26a219c06cfb0f468eef8d97055fa814f97 # v1.0.53
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # zizmor: ignore[secrets-outside-env]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The inline # zizmor: ignore[secrets-outside-env] is a no-op for the CI job this PR adds: secrets-outside-env only fires under zizmor's auditor persona, but zizmor.yml doesn't set a persona: input so zizmor-action runs the default regular persona where this audit never triggers. This means the PR description's claim that the suppression silences "the one remaining warning" is incorrect, and the discussed follow-up (creating a GitHub environment for ANTHROPIC_API_KEY) is not actually required for a clean run with the configured persona — consider either dropping the suppression or explicitly setting persona: auditor if you want that audit enforced.

Extended reasoning...

What's happening

The PR adds # zizmor: ignore[secrets-outside-env] to .github/workflows/claude.yml:39 and the PR description says this is needed to silence "the one remaining warning" so the new zizmor job is green from the start. The in-thread discussion likewise treats creating a GitHub environment for ANTHROPIC_API_KEY as a tracked follow-up that would let this suppression be removed.

But the secrets-outside-env audit is gated behind zizmor's auditor persona. The new zizmor.yml workflow uses zizmorcore/zizmor-action@v0.5.6 with no persona: input, which means the action runs with its default — the regular persona, the same as a bare uvx zizmor invocation. Under regular (and even under the stricter pedantic), secrets-outside-env simply never fires.

Step-by-step proof (zizmor 1.25.2, the version pinned by zizmor-action v0.5.6)

  1. Check out this branch.
  2. uvx zizmor .github/workflows/ (regular persona, with the inline ignore) → No findings to report. Good job! (29 suppressed)
  3. Remove the # zizmor: ignore[secrets-outside-env] comment from claude.yml:39 and re-run uvx zizmor .github/workflows/ → identical: No findings to report. Good job! (29 suppressed). The suppression had no effect.
  4. uvx zizmor --persona pedantic .github/workflows/claude.yml (no ignore) → still no secrets-outside-env finding.
  5. Only uvx zizmor --persona auditor .github/workflows/claude.yml surfaces warning[secrets-outside-env] at line 39.

Why this matters

The suppression is dead annotation under the workflow's actual configuration. There's no functional impact on CI — the job stays green either way — but it does carry a misleading premise into the codebase and the PR record:

  • The PR description says the suppression is what makes the run clean. It isn't; the run is already clean under regular.
  • The in-thread agreement ("Suppressed inline for now; happy to follow up once the environment exists") tracks a follow-up — creating a GitHub environment so the suppression can be removed — that won't actually change zizmor's output. Whoever picks that up will do the repo-settings work, delete the comment, and observe nothing.

On the counter-argument that this is harmless future-proofing

It's true the comment becomes load-bearing if the team later switches to persona: auditor. But that's not what this PR sets up, and the PR text explicitly frames the suppression as required for the current configuration. A comment that documents an audit zizmor isn't running, framed as if it were, is exactly the kind of thing that costs the next reader time. If the team wants the auditor audit suite, set persona: auditor in zizmor.yml (in which case the suppression is correct and load-bearing). If not, drop the suppression to keep the annotations honest.

Suggested fix

Either:

# .github/workflows/claude.yml — remove the no-op annotation
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

or, if you actually want secrets-outside-env (and the rest of the auditor suite) enforced:

# .github/workflows/zizmor.yml
- name: Run zizmor 🌈
  uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
  with:
    persona: auditor

(Note: enabling auditor will surface several other findings beyond this one, so it's not a drop-in change.)

@maxisbey
Copy link
Copy Markdown
Contributor

Two pre-existing findings the new workflow will surface as medium Code Scanning alerts on main once this merges, in case you want to land clean. Both are out of the diff so I can't make them click-apply suggestions:

.github/dependabot.ymldependabot-cooldown

Dependabot has no delay between a release publishing and a bump PR opening, so a compromised release in a github-action could get proposed before it's noticed and yanked. A cooldown only considers versions that have been live for a window:

updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: monthly
    cooldown:
      default-days: 14
    groups:
      github-actions:
        patterns:
          - "*"

.github/workflows/weekly-lockfile-update.yml:34ref-version-mismatch

The SHA c0f553fe… is actually tag v8.1.0; the # v7 comment went stale when dependabot bumped the pin without updating the comment:

      - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0

Both are pre-existing, neither blocks this PR — happy to defer to a follow-up if you'd rather keep this scoped to the workflow hardening.

AI Disclaimer

@maxisbey
Copy link
Copy Markdown
Contributor

Two pre-existing findings the new workflow will surface as medium Code Scanning alerts on main once this merges, in case you want to land clean. Both are out of the diff so I can't make them click-apply suggestions:

.github/dependabot.ymldependabot-cooldown

Dependabot has no delay between a release publishing and a bump PR opening, so a compromised release in a github-action could get proposed before it's noticed and yanked. A cooldown only considers versions that have been live for a window:

updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: monthly
    cooldown:
      default-days: 14
    groups:
      github-actions:
        patterns:
          - "*"

.github/workflows/weekly-lockfile-update.yml:34ref-version-mismatch

The SHA c0f553fe… is actually tag v8.1.0; the # v7 comment went stale when dependabot bumped the pin without updating the comment:

      - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0

Both are pre-existing, neither blocks this PR — happy to defer to a follow-up if you'd rather keep this scoped to the workflow hardening.

AI Disclaimer

@Kludex happy for you to ignore these btw and merge as they're nits and can be a follow up. will approve now but if you fix now I'll just reapprove :)

Copy link
Copy Markdown
Contributor

@maxisbey maxisbey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved with above nits as optional fixes, up to you

@Kludex Kludex merged commit e8e6484 into main May 20, 2026
58 of 59 checks passed
@Kludex Kludex deleted the ci/add-zizmor branch May 20, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants